﻿using System;
using System.Linq;
using System.Text.RegularExpressions;
using Boggle.Net.Model;
using Boggle.Net.Tests.Stubs;
using Boggle.Net.ViewModels;
using Caliburn.Micro;
using NUnit.Framework;
using TechTalk.SpecFlow;

namespace Boggle.Net.Tests.Specifications
{
    [Binding]
    public class Steps
    {
        [Given(@"I am verifying words against the (.+) word list")]
        public void GivenIAmVerifyingWordsAgainstTheWordListNamed(string name)
        {
            ScenarioContext.Current.Set(IoC.Get<IWordList>);
        }

        [When(@"the resulting word is ((?:un)?recognised)")]
        public void WordIsRecognised(string option)
        {
            var shouldBeKnown = option == "recognised";
            Assert.AreEqual(shouldBeKnown, IoC.Get<IWordList>().ContainsWord(CurrentGame.Tray.SelectedWord));
        }

        [Given(@"I have started a new game as follows:")]
        public void StartANewGame(Table table)
        {
            Assert.AreEqual(Tray.Rows, table.RowCount);

            var tray = Tray.FilledWith(String.Concat(table.Rows.Select(row => row[0])).ToCharArray());
            ScenarioContext.Current.Set(new GameViewModel(tray));
        }

        [Then(@"the game should be ((?:in)?complete)")]
        public void GameStatusShouldBe(string status)
        {
            Assert.AreEqual(status == "complete", CurrentGame.IsFinished);
        }

        [Then(@"the game score should be (\d+)")]
        public void GameScoreShouldBe(int score)
        {
            Assert.AreEqual(score, CurrentGame.Score);
        }

        [Then(@"there should be (\d+) minutes remaining to complete the game")]
        public void TimeRemainingToCompleteTheGameShouldBe(int minutes)
        {
            Assert.AreEqual(minutes, (int) CurrentGame.GameTime.TotalMinutes);
        }

        [When(@"the maximum time has elapsed")]
        public void MaximumTimeHasElapsed()
        {
            var stopwatch = (Stopwatch)IoC.Get<IStopwatch>();
            stopwatch.TotalRunningTime = TimeSpan.FromMinutes(3.5);
        }

        [Given(@"I have selected the letters '(\w+)' at cells (.*)")]
        [When(@"I select the letters '(\w+)' at cells (.*)")]
        public void SelectTheLettersAtCells(string word, string path)
        {
            var cellList = from location in ParsePathExpression(path)
                           join cell in CurrentGame.Tray.CellList on location equals cell.Location
                           select cell;

            foreach (var cell in cellList)
            {
                Assert.IsFalse(cell.IsSelected);
                cell.IsSelected = true;
            }

            Assert.AreEqual(word, CurrentGame.Tray.SelectedWord);
        }

        [Given(@"I have played the following words:")]
        [When(@"I play the following words:")]
        public void PlayTheWords(Table table)
        {
            foreach (var row in table.Rows)
            {
                SelectTheLettersAtCells(row["Word"], row["Path"]);
                AcceptTheSelection();
            }
        }

        [When(@"I accept the word")]
        public void AcceptTheSelection()
        {
            CurrentGame.AddSelection();
        }

        [When(@"I re-select the last letter")]
        public void ReSelectTheLastLetter()
        {
            var cell = CurrentGame.Tray.Selection.Last();
            cell.IsSelected = !cell.IsSelected;
        }

        [When(@"I clear the selection")]
        public void ClearTheSelection()
        {
            CurrentGame.Tray.Selection.Clear();
        }

        [When(@"I display the solution")]
        public void DisplayTheSolution()
        {
            CurrentGame.Solve();
        }

        [Then(@"the timer (should|should not) be running")]
        public void TimerShouldBeRunning(string option)
        {
            Assert.AreEqual(option == "should", IoC.Get<IStopwatch>().IsRunning);
        }

        [Then(@"the word list should have (\d+) word(?:s)?")]
        public void WordCountShouldBe(int count)
        {
            Assert.AreEqual(count, CurrentGame.FoundPlays.Count());
        }

        [Then(@"the selection should be cleared")]
        public void SelectionShouldBeEmpty()
        {
            Assert.IsTrue(CurrentGame.Tray.Selection.IsEmpty);
        }

        [Then(@"the selection should have (\d+) letters")]
        public void SelectionCountShouldBe(int count)
        {
            Assert.AreEqual(count, CurrentGame.Tray.Selection.Count());
        }

        [Then(@"the selection should contain the word '(.+)'")]
        public void SelectionShouldContain(string word)
        {
            Assert.AreEqual(word, CurrentGame.Tray.SelectedWord);
        }

        [Then(@"I should be ((?:un)?able) to clear the selection")]
        public void SelectionShouldBeClearable(string action)
        {
            Assert.AreEqual(action == "able", CurrentGame.CanClearSelection);
        }

        [Then(@"I should be ((?:un)?able) to add the word")]
        public void WordShouldBeAcceptable(string action)
        {
            Assert.AreEqual(action == "able", CurrentGame.CanAddSelection);
        }

        [StepArgumentTransformation(@"((\(\d+,\s+\d+\)\s*)+)")]
        public static Tray.Location[] ParsePathExpression(string pathExpression)
        {
            var groups = Regex.Match(pathExpression, @"((\((?<Col>\d+),\s+(?<Row>\d+)\)\s*)+)").Groups;

            var cols = groups["Col"].Captures;
            var rows = groups["Row"].Captures;

            var result = new Tray.Location[rows.Count];

            for (var i = 0; i < rows.Count; ++i)
                result[i] = new Tray.Location(Convert.ToInt32(cols[i].Value), Convert.ToInt32(rows[i].Value));

            return result;
        }

        private static GameViewModel CurrentGame
        {
            get { return ScenarioContext.Current.Get<GameViewModel>(); }
        }
    }
}
